﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web.Mvc;
using System.Web.Routing;
using System.Reflection;
using System.Web;
using System.Threading;
using System.Globalization;
using MvcEngine.Core.Localization;
using MvcEngine.Core.Utils;
using MvcEngine.Core;
using MvcEngine.Mvc.Results;
using MvcEngine.Core.Configuration;
using MvcEngine.Core.Repositories;

using MvcEngine.Mvc.Models.ViewModels;
using MvcEngine.Core.Helpers;
using MvcEngine.Mvc.Filters;
using System.Collections;
using MvcEngine.Mvc.Extensions;

namespace MvcEngine.Mvc.Controllers
{
    public class BaseController : Controller
    {
        protected virtual string GetResource(string key)
        {
            return ResourceReader.Current.GetString(key);
        }

        protected override void Initialize(RequestContext requestContext)
        {
            string area = (string)requestContext.RouteData.DataTokens["Area"];
            if (!string.IsNullOrEmpty(area))
            {
                requestContext.HttpContext.Items["Area"] = area;
            }

            base.Initialize(requestContext);
        }

        protected override void OnException(ExceptionContext filterContext)
        {
            LogHelper.Current.WriteError(filterContext.Exception.Message, filterContext.Exception);
            ControllerContext controllerContext = filterContext.Controller.ControllerContext;
            filterContext.ExceptionHandled = true;

            if (filterContext.Exception is PageNotFoundException)
                controllerContext.RouteData.Values["viewName"] = "PageNotFound";
            else if (filterContext.Exception is PermissionException)
            {
                if (SiteContext.Current.Site.LoginPage.HasValue && Url.Page(SiteContext.Current.Site.LoginPage.Value) != Request.Url.AbsolutePath)
                {
                    Response.Redirect(Url.Page(SiteContext.Current.Site.LoginPage.Value), true);
                    return;
                }
                else
                    controllerContext.RouteData.Values["viewName"] = "PageAccessForbidden";
            }
            else
                controllerContext.RouteData.Values["viewName"] = "Error";

            RedirectToErrorPage(controllerContext, "Error", "Error");
        }

        protected virtual void RedirectToErrorPage(ControllerContext context, string action, string controllerName)
        {
            context.RouteData.Values["action"] = action;
            context.RouteData.Values["controller"] = controllerName;

            var requestContext = context.RequestContext;

            requestContext.HttpContext.Response.StatusCode = 500;
            //requestContext.HttpContext.Response.Status = "500";

            var factory = ControllerBuilder.Current.GetControllerFactory();
            var controller = factory.CreateController(requestContext, controllerName);
            ((ControllerBase)controller).ValidateRequest = false;
            controller.Execute(requestContext);
        }

        protected override void HandleUnknownAction(string actionName)
        {
            try
            {
                base.HandleUnknownAction(actionName);
            }
            catch (HttpException e)
            {
                LogHelper.Current.WriteError("Action " + actionName + " was not found in controller " + this.GetType(), e);
            }
        }

        public new ActionResult Json(object data)
        {
            return new MvcEngineJsonResult() { Data = data, JsonRequestBehavior = JsonRequestBehavior.DenyGet };
        }

        public ActionResult JsonInvalidResource(string resourceKey)
        {
            return JsonInvalid(GetResource(resourceKey));
        }

        public ActionResult JsonInvalid(string errorMessage)
        {
            return new MvcEngineJsonResult()
            {
                Data = new
                {
                    model = "invalid",
                    errors = new List<string>() { errorMessage }
                },
                JsonRequestBehavior = JsonRequestBehavior.DenyGet
            };
        }

        public new ActionResult JsonInvalid()
        {
            return new MvcEngineJsonResult()
            {
                Data = new
                {
                    model = "invalid",
                    errors = ModelState.GetErrors()
                },
                JsonRequestBehavior = JsonRequestBehavior.DenyGet
            };
        }

        public new ActionResult JsonValid()
        {
            return new MvcEngineJsonResult() { Data = new { model = "valid" }, JsonRequestBehavior = JsonRequestBehavior.DenyGet };
        }

        public ActionResult Text(string text)
        {
            return new TextActionResult(text);
        }

        ///<summary>
        /// Gets the name from the type by trimming "controller" and "subcontroller" from the type name.  The subcontroller action must match the controller name.
        ///</summary>
        ///<returns></returns>
        public string GetControllerName()
        {
            string typeNameLowered = GetType().Name.ToLowerInvariant();
            int index = typeNameLowered.IndexOf("subcontroller");
            if (index == -1)
            {
                index = typeNameLowered.IndexOf("controller");
            }

            if (index == -1)
            {
                return typeNameLowered;
            }

            return typeNameLowered.Substring(0, index);
        }

        public virtual Action GetResult(ControllerBase parentController, string controllerNamespace, string action, bool isDefaultAction, IDictionary<string, object> values)
        {
            RequestContext requestContext = GetNewRequestContextFromController(parentController, action, isDefaultAction, values);
            this.ControllerContext = new ControllerContext(requestContext, parentController);

            if (isDefaultAction)
                this.ControllerContext.Controller.ValidateRequest = false;

            return () =>
            {
                if (values["moduleName"] != null)
                {
                    requestContext.HttpContext.Items["moduleName"] = (string)values["moduleName"];
                    System.Web.HttpContext.Current.Items["moduleName"] = (string)values["moduleName"];
                }
                string requiredString = requestContext.RouteData.GetRequiredString("controller");

                if (!string.IsNullOrEmpty(controllerNamespace))
                {
                    object namespaces;
                    requestContext.RouteData.DataTokens.TryGetValue("Namespaces", out namespaces);

                    if (namespaces != null && namespaces is IEnumerable<string>)
                    {
                        List<string> newNamespaces = new List<string>((IEnumerable<string>)namespaces);
                        newNamespaces.Add(controllerNamespace);
                        requestContext.RouteData.DataTokens["Namespaces"] = newNamespaces.ToArray();
                    }
                }

                IControllerFactory controllerFactory = ControllerBuilder.Current.GetControllerFactory();
                IController controller = controllerFactory.CreateController(requestContext, requiredString);

                if (controller == null)
                {
                    throw new InvalidOperationException("Controller was not found");
                }
                try
                {
                    controller.Execute(requestContext);
                }
                finally
                {
                    controllerFactory.ReleaseController(controller);
                }
            };
        }

        ///<summary>
        /// Gets new RequestContext using objects from parent controller.  This ensures subcontrollers have their own state that doesn't conflict with the parent.
        ///</summary>
        ///<param name="parentController">Parent controller</param>
        ///<returns>RequestContext</returns>
        public RequestContext GetNewRequestContextFromController(ControllerBase parentController, string action, bool isDefaultAction, IDictionary<string, object> values)
        {
            RouteData parentRouteData = parentController.ControllerContext.RouteData;
            var routeData = new RouteData(parentRouteData.Route, parentRouteData.RouteHandler);
            string controllerName = GetControllerName();

            foreach (var item in parentRouteData.Values)
            {
                routeData.Values[item.Key] = item.Value;
            }

            foreach (var item in parentRouteData.DataTokens)
            {
                routeData.DataTokens[item.Key] = item.Value;
            }

            foreach (var item in values)
            {
                routeData.Values[item.Key] = item.Value;
            }

            routeData.Values["controller"] = controllerName;
            routeData.Values["action"] = action;

            return new RequestContext(parentController.ControllerContext.HttpContext, routeData);
        }
    }
}
